/**
* Licensed to the Austrian Association for Software Tool Integration (AASTI)
* under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. The AASTI licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openengsb.itests.util;
import static org.ops4j.pax.exam.CoreOptions.maven;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.OptionUtils.combine;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.debugConfiguration;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDistributionConfiguration;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.inject.Inject;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.mgt.SecurityManager;
import org.junit.Before;
import org.openengsb.connector.usernamepassword.Password;
import org.openengsb.core.api.security.AuthenticationContext;
import org.openengsb.core.api.security.service.UserDataManager;
import org.openengsb.core.workflow.api.RuleManager;
import org.openengsb.core.workflow.api.model.RuleBaseElementId;
import org.openengsb.core.workflow.api.model.RuleBaseElementType;
import org.openengsb.core.workflow.drools.OsgiHelper;
import org.openengsb.domain.auditing.AuditingDomain;
import org.openengsb.domain.authentication.AuthenticationDomain;
import org.openengsb.domain.authentication.AuthenticationException;
import org.openengsb.domain.authorization.AuthorizationDomain;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.ProbeBuilder;
import org.ops4j.pax.exam.TestProbeBuilder;
import org.ops4j.pax.exam.karaf.options.ConfigurationPointer;
import org.ops4j.pax.exam.karaf.options.LogLevelOption;
import org.ops4j.pax.exam.karaf.options.configs.ManagementCfg;
import org.ops4j.pax.exam.karaf.options.configs.WebCfg;
import org.ops4j.pax.exam.options.extra.VMOption;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableMap;
public abstract class AbstractExamTestHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExamTestHelper.class);
/*
* to configure loglevel and debug-flag, create a file called itests.local.properties in src/test/resources. This
* file should only contain simple properties. You can use debug=true and loglevel=INFO in this file. Additional
* possible properties are debugport=5005 and hold=true. The debugport option specifies the port where the container
* is reachable and the hold option if the container should wait for a debugger to be attached or not.
*/
private static final int DEBUG_PORT = 5005;
private static final String LOG_LEVEL = "ERROR";
public static final long DEFAULT_TIMEOUT = 90000;
@Inject
private BundleContext bundleContext;
private AuthenticationContext authenticationContext;
@Before
public void setupFramework() throws Exception {
waitForFrameworkToStart();
waitForRequiredTasks();
}
private void waitForRequiredTasks() throws Exception {
authenticationContext = getOsgiService(AuthenticationContext.class);
waitForUserDataInitializer();
RuleManager rm = getOsgiService(RuleManager.class);
int count = 0;
while (rm.getGlobalType("auditing") == null) {
LOGGER.warn("waiting for auditing to finish init");
waitasec();
if (count++ > 100) {
throw new IllegalStateException("auditing-config did not finish in time");
}
}
count = 0;
while (!rm.listImports().contains(OsgiHelper.class.getName())) {
LOGGER.warn("waiting for auditing to finish init");
waitasec();
if (count++ > 100) {
throw new IllegalStateException("auditing-config did not finish in time");
}
}
count = 0;
while (rm.get(new RuleBaseElementId(RuleBaseElementType.Process, "humantask")) == null) {
LOGGER.warn("waiting for taskboxConfig to finish init");
waitasec();
if (count++ > 100) {
throw new IllegalStateException("taskbox-config did not finish in time");
}
}
authenticationContext = getOsgiService(AuthenticationContext.class);
}
private void waitForFrameworkToStart() throws Exception {
waitForOsgiBundle("org.openengsb.domain.authentication");
waitForOsgiBundle("org.openengsb.domain.authorization");
waitForOsgiBundle("org.openengsb.connector.usernamepassword");
waitForOsgiBundle("org.openengsb.framework.common");
waitForOsgiBundle("org.openengsb.framework.util");
waitForOsgiBundle("org.openengsb.framework.services");
waitForOsgiBundle("org.openengsb.connector.memoryauditing");
queryOsgiService(AuditingDomain.class, null, 10000, true);
queryOsgiService(AuthenticationDomain.class, "(location.root=authentication-root)", 25000, true);
queryOsgiService(AuthorizationDomain.class, "(location.root=authorization-root)", 25000, true);
}
private static final Map<Integer, String> STATES = ImmutableMap.of(1, "UNINSTALLED", 2, "INSTALLED", 4, "RESOLVED",
8, "STARTING", 32, "ACTIVE");
private void waitasec() throws InterruptedException {
for (Bundle b : bundleContext.getBundles()) {
if (b.getState() == Bundle.ACTIVE) {
continue;
}
LOGGER.info(String.format("[%s]-[%s] - %s", b.getBundleId(), STATES.get(b.getState()),
b.getSymbolicName()));
}
Thread.sleep(1000);
}
protected <T> T getOsgiService(Class<T> type, long timeout) {
return getOsgiService(type, null, timeout);
}
protected <T> T getOsgiService(Class<T> type) {
return getOsgiService(type, null, DEFAULT_TIMEOUT);
}
protected Bundle getInstalledBundle(String symbolicName) {
for (Bundle b : bundleContext.getBundles()) {
if (b.getSymbolicName().equals(symbolicName)) {
return b;
}
}
return null;
}
protected void waitForSiteToBeAvailable(String urlToWatchFor, Integer maxWaitTime) throws InterruptedException {
Integer localCounter = maxWaitTime;
while (localCounter != 0) {
if (isUrlReachable(urlToWatchFor)) {
return;
}
waitasec();
localCounter--;
}
throw new IllegalStateException(String.format("Couldn't reach page %s within %s seconds", urlToWatchFor,
maxWaitTime));
}
@SuppressWarnings("deprecation")
protected boolean isUrlReachable(String url) {
URL downloadUrl;
InputStream is = null;
DataInputStream dataInputStream;
try {
downloadUrl = new URL(url);
is = downloadUrl.openStream();
dataInputStream = new DataInputStream(new BufferedInputStream(is));
while (dataInputStream.readLine() != null) {
return true;
}
} catch (Exception e) {
// well... what should we say; this could happen...
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException ioe) {
}
}
return false;
}
protected void waitForOsgiBundle(String symbolicName) throws Exception {
waitForOsgiBundle(symbolicName, DEFAULT_TIMEOUT);
}
protected void waitForOsgiBundle(String symbolicName, long timeout) throws Exception {
int sleepTime = 1000;
int i = 0;
Bundle b = null;
do {
b = getInstalledBundle(symbolicName);
if (b.getState() == Bundle.ACTIVE) {
break;
}
// break the loop after timeout to avoid endless loop
if ((i * sleepTime) >= timeout) {
throw new RuntimeException("bundle " + symbolicName
+ " didn't start after " + timeout / 1000 + " seconds");
}
Thread.sleep(sleepTime);
i++;
} while (b.getState() != Bundle.ACTIVE);
}
protected <T> T getOsgiService(Class<T> type, String filter, long timeout) {
return queryOsgiService(type, filter, timeout, true);
}
protected Boolean isOsgiServiceAvailable(Class<?> type, String filter) {
return queryOsgiService(type, filter, 100, false) != null;
}
protected <T> T queryOsgiService(Class<T> type, String filter, long timeout, boolean throwException) {
ServiceTracker<T, T> tracker;
try {
String flt;
if (filter != null) {
if (filter.startsWith("(")) {
flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")" + filter + ")";
} else {
flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")(" + filter + "))";
}
} else {
flt = "(" + Constants.OBJECTCLASS + "=" + type.getName() + ")";
}
Filter osgiFilter = FrameworkUtil.createFilter(flt);
tracker = new ServiceTracker<T, T>(bundleContext, osgiFilter, null);
tracker.open(true);
// Note that the tracker is not closed to keep the reference
// This is buggy, as the service reference may change i think
T svc = tracker.waitForService(timeout);
if (svc == null && throwException) {
@SuppressWarnings("rawtypes")
Dictionary dic = bundleContext.getBundle().getHeaders();
LOGGER.error("Test bundle headers: {}", explode(dic));
for (ServiceReference<?> ref : asCollection(bundleContext.getAllServiceReferences(null, null))) {
LOGGER.error("ServiceReference: {}", ref);
}
for (ServiceReference<?> ref : asCollection(bundleContext.getAllServiceReferences(null, flt))) {
LOGGER.error("Filtered ServiceReference: {}", ref);
}
throw new RuntimeException("Gave up waiting for service " + flt);
}
return svc;
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException("Invalid filter", e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("rawtypes")
private static String explode(Dictionary dictionary) {
Enumeration keys = dictionary.keys();
StringBuffer result = new StringBuffer();
while (keys.hasMoreElements()) {
Object key = keys.nextElement();
result.append(String.format("%s=%s", key, dictionary.get(key)));
if (keys.hasMoreElements()) {
result.append(", ");
}
}
return result.toString();
}
/*
* Provides an iterable collection of references, even if the original array is null
*/
private static Collection<ServiceReference<?>> asCollection(ServiceReference<?>[] references) {
List<ServiceReference<?>> result = new LinkedList<ServiceReference<?>>();
if (references != null) {
for (ServiceReference<?> reference : references) {
result.add(reference);
}
}
return result;
}
protected BundleContext getBundleContext() {
return bundleContext;
}
protected static String getWorkingDirectory() {
return "target/paxrunner/features/";
}
protected void authenticateAsAdmin() throws InterruptedException, AuthenticationException {
authenticate("admin", "password");
}
protected void authenticate(String user, String password) throws InterruptedException, AuthenticationException {
authenticationContext.login(user, new Password(password));
}
protected void waitForUserDataInitializer() throws InterruptedException {
SecurityManager sm = null;
int count = 0;
while (sm == null) {
try {
sm = SecurityUtils.getSecurityManager();
} catch (UnavailableSecurityManagerException e) {
LOGGER.warn("waiting for security-manager to be set");
waitasec();
}
if (count++ > 100) {
throw new IllegalStateException("security-manager was not set in time");
}
}
UserDataManager userDataManager = getOsgiService(UserDataManager.class, "(internal=true)", 20000);
count = 0;
while (userDataManager.getUserList().isEmpty()) {
LOGGER.warn("waiting for users to be initialized");
waitasec();
if (count++ > 100) {
throw new IllegalStateException("user-data-initializer did not finish in time");
}
}
getOsgiService(AuthenticationDomain.class, "(connector=usernamepassword)", 15000);
}
@ProbeBuilder
public TestProbeBuilder probeConfiguration(TestProbeBuilder probe) throws IOException {
InputStream stream = ClassLoader.getSystemResourceAsStream("META-INF/maven/dependencies.properties");
Properties depProperties = new Properties();
depProperties.load(stream);
String projectVersion = ((String) depProperties
.get("org.openengsb.domain/org.openengsb.domain.example/version"))
.replace("-", ".");
probe.setHeader("Project-Version", projectVersion);
return probe;
}
public static Option[] baseConfiguration() throws Exception {
// String loglevel = LOG_LEVEL;
String debugPort = Integer.toString(DEBUG_PORT);
boolean hold = true;
boolean debug = false;
InputStream paxLocalStream = ClassLoader.getSystemResourceAsStream("itests.local.properties");
if (paxLocalStream != null) {
Properties properties = new Properties();
properties.load(paxLocalStream);
// loglevel = (String) ObjectUtils.defaultIfNull(properties.getProperty("loglevel"), loglevel);
debugPort = "5005"; // (String) ObjectUtils.defaultIfNull(properties.getProperty("debugport"), debugPort);
debug = true; // ObjectUtils.equals(Boolean.TRUE.toString(), properties.getProperty("debug"));
hold = true; // ObjectUtils.equals(Boolean.TRUE.toString(), properties.getProperty("hold"));
}
Properties portNames = new Properties();
InputStream portsPropertiesFile = ClassLoader.getSystemResourceAsStream("ports.properties");
if (portsPropertiesFile == null) {
throw new IllegalStateException("ports-configuration not found");
}
portNames.load(portsPropertiesFile);
LOGGER.warn("running itests with the following port-config");
LOGGER.warn(portNames.toString());
// LogLevel realLogLevel = transformLogLevel(loglevel);
Option[] mainOptions =
new Option[]{
new VMOption("-Xmx2048m"),
new VMOption("-XX:MaxPermSize=256m"),
karafDistributionConfiguration().frameworkUrl(
maven().groupId("org.openengsb.framework").artifactId("openengsb-framework").type("zip")
.versionAsInProject()),
logLevel(LogLevelOption.LogLevel.ERROR),
editConfigurationFilePut(WebCfg.HTTP_PORT, (String) portNames.get("jetty.http.port")),
editConfigurationFilePut(ManagementCfg.RMI_SERVER_PORT, (String) portNames.get("rmi.server.port")),
editConfigurationFilePut(ManagementCfg.RMI_REGISTRY_PORT, (String) portNames.get("rmi.registry.port")),
editConfigurationFilePut(new ConfigurationPointer("etc/org.openengsb.infrastructure.jms.cfg",
"openwire"), (String) portNames.get("jms.openwire.port")),
editConfigurationFilePut(new ConfigurationPointer("etc/org.openengsb.infrastructure.jms.cfg",
"stomp"), (String) portNames.get("jms.stomp.port")),
mavenBundle(maven().groupId("org.openengsb.wrapped").artifactId("net.sourceforge.htmlunit-all")
.versionAsInProject())};
mainOptions = combine(mainOptions, getDefaultEDBConfiguration());
if (debug) {
return combine(mainOptions, debugConfiguration(debugPort, hold));
}
return mainOptions;
}
private static Option[] getDefaultEDBConfiguration() {
String cfg = "etc/org.openengsb.infrastructure.jpa.cfg";
return new Option[]{
editConfigurationFilePut(cfg, "url", "jdbc:h2:mem:itests"),
editConfigurationFilePut(cfg, "driverClassName", "org.h2.jdbcx.JdbcDataSource"),
editConfigurationFilePut(cfg, "username", ""),
editConfigurationFilePut(cfg, "password", "")
};
}
public String getConfigProperty(String config, String name) throws IOException {
ConfigurationAdmin cm = getOsgiService(ConfigurationAdmin.class);
Configuration configuration = cm.getConfiguration(config);
return (String) configuration.getProperties().get(name);
}
// private static LogLevel transformLogLevel(String logLevel) {
// switch (logLevel) {
// case "ERROR":
// return LogLevel.ERROR;
// case "WARN":
// return LogLevel.WARN;
// case "INFO":
// return LogLevel.INFO;
// case "DEBUG":
// return LogLevel.DEBUG;
// case "TRACE":
// return LogLevel.TRACE;
// default:
// return LogLevel.WARN;
// }
// }
protected String getOsgiProjectVersion() {
return bundleContext.getBundle().getHeaders().get("Project-Version");
}
}